package com.xsf.pay.common.inteceptor;

import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONObject;
import com.xsf.pay.common.constants.RedisConstant;
import com.xsf.pay.common.filter.AccessRequestWrapper;
import com.xsf.pay.common.properties.EncryptionProperties;
import com.xsf.pay.common.utils.MD5Utils;
import com.xsf.pay.common.utils.RSAUtilSegment;
import com.xsf.pay.common.utils.RequestUtil;
import com.xsf.pay.service.RedisService;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.TimeUnit;

/**
 * 加解密拦截器
 *
 * @author 18961
 */
@Component
@Slf4j
public class ParamInterceptor implements HandlerInterceptor {
    @Resource
    private EncryptionProperties encryptionProperties;
    @Resource
    private RedisService redisService;

    @Override
    public boolean preHandle(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull Object handler) throws Exception {
        if (encryptionProperties.getRequestOpen() && request.getContentType().contains("application/json")) {
            try {
                // 获取加密参数
                String resultBody = RequestUtil.getBodyString(request);
                // 使用 RSA 解密
                AccessRequestWrapper accessRequestWrapper = new AccessRequestWrapper(request);
                String decryptedData = RSAUtilSegment.privateKeyDecrypt(resultBody, encryptionProperties.getRequestPrivateKey());
                Assert.notNull(decryptedData);
                decryptedData = new String(Base64.decodeBase64(decryptedData));
                JSONObject reqJson = JSONObject.parseObject(decryptedData);
                log.info("校验前的报文：{}",reqJson);
                //计算时间戳
                if(!validateTimestamp(reqJson)){
                    throw new RuntimeException("时间戳错误");
                }
                //计算Nonce
                if(!validateNonce(reqJson)){
                    throw new RuntimeException("随机值错误");
                }
                //计算签名
                if(!validateSign(reqJson)){
                    throw new RuntimeException("签名错误");
                }
                // 将解密后的数据存入 request 属性
                accessRequestWrapper.setBody(reqJson.getString("data"));
            } catch (Exception e) {
                log.error("解密失败，请检查参数", e);
                response.setStatus(500);
                response.setContentType("text/plain;charset=UTF-8");
                response.getWriter().write("解密失败，请检查参数");
                return false;
            }
        }
        return true;
    }

    /**
     * 签名是否通过
     * @param reqJson 请求参数
     * @return true/false签名通过
     */
    private boolean validateSign(JSONObject reqJson){
        if(!reqJson.containsKey("sign")){
            return false;
        }
        JSONObject dataJson = reqJson.getJSONObject("data");
        dataJson.put("timestamp",reqJson.getString("timestamp"));
        dataJson.put("nonceStr", reqJson.getString("nonceStr"));
        Map<String, Object> sortedMap = new TreeMap<>(dataJson);
        String sign = reqJson.getString("sign");
        StringBuilder argBuilder = new StringBuilder();
        for (Map.Entry<String, Object> entry : sortedMap.entrySet()) {
            if ("sign".equals(entry.getKey())) {
                continue;
            }
            argBuilder.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
        }
        String arg = argBuilder.toString();
        // 去掉最后一个多余的 &
        if (arg.endsWith("&")) {
            arg = arg.substring(0, arg.length() - 1);
        }
        String signStr = MD5Utils.encrypt(arg).toLowerCase();
        return signStr.equals(sign);
    }

    /**
     * 时间戳是否通过
     * @param reqJson 请求参数
     * @return 时间戳是否在时间范围内
     */
    private boolean validateTimestamp(JSONObject reqJson){
        if(!reqJson.containsKey("timestamp")){
            return false;
        }
        String timestamp = reqJson.getString("timestamp");
        long timestampLong = Long.parseLong(timestamp);
        long currentTimeMillis = System.currentTimeMillis();
        long diff = Math.abs(currentTimeMillis - timestampLong);
        return diff <= encryptionProperties.getRequestTimeout();
    }
    /**
     * 校验 nonce_str
     * @param reqJson 请求参数
     * @return true/false nonce_str 通过
     */
    private boolean validateNonce(JSONObject reqJson) {
        if(!reqJson.containsKey("nonceStr")){
            return false;
        }
        String nonceStr = reqJson.getString("nonceStr");
        //校验是否16位
        if (StrUtil.length(nonceStr) != 16) {
            return false;
        }
        String key = StrUtil.format(RedisConstant.NONCE_STR, nonceStr);
        // 检查 nonce_str 是否已经存在
        if (redisService.hasKey(key)) {
            return false;
        }
        // 将 nonce_str 存入 Redis，并设置过期时间，例如 5 分钟
        redisService.set(key, nonceStr, encryptionProperties.getRequestTimeout(), TimeUnit.MILLISECONDS);
        return true;
    }
}
